﻿namespace SenenFernandez.SmartExtensionMethods.Interactivity
{
    using Interactivity;
    using System;
    using System.Windows;
    using System.Windows.Input;

    /// <summary>
    /// This <see cref="System.Windows.Interactivity.TriggerAction" /> can be
    /// used to bind any event on any FrameworkElement to an <see cref="ICommand" />.
    /// Typically, this element is used in XAML to connect the attached element
    /// to a command located in a ViewModel. This trigger can only be attached
    /// to a FrameworkElement or a class deriving from FrameworkElement.
    /// </summary>
    public class EventCommand : TriggerAction<DependencyObject>
    {
        public static readonly DependencyProperty CommandParameterProperty =
            DependencyProperty.Register("CommandParameter", typeof (object), typeof (EventCommand),
                new PropertyMetadata(null, (s, e) =>
                {
                    var sender = s as EventCommand;
                    if (sender == null)
                    {
                        return;
                    }

                    if (sender.AssociatedObject == null)
                    {
                        return;
                    }

                    sender.EnableDisableElement();
                }));

        public static readonly DependencyProperty CommandProperty =
            DependencyProperty.Register("Command", typeof (ICommand), typeof (EventCommand),
                new PropertyMetadata(null, (s, e) => OnCommandChanged(s as EventCommand, e)));

        public static readonly DependencyProperty MustToggleIsEnabledProperty =
            DependencyProperty.Register("MustToggleIsEnabled", typeof (bool), typeof (EventCommand),
                new PropertyMetadata(false, (s, e) =>
                {
                    var sender = s as EventCommand;
                    if (sender == null)
                    {
                        return;
                    }

                    if (sender.AssociatedObject == null)
                    {
                        return;
                    }

                    sender.EnableDisableElement();
                }));

        private object commandParameterValue;

        private bool? mustToggleValue;

        /// <summary>
        /// Gets or sets the ICommand that this trigger is bound to. This is a DependencyProperty.
        /// </summary>
        public ICommand Command
        {
            get { return (ICommand) GetValue(CommandProperty); }
            set { SetValue(CommandProperty, value); }
        }

        /// <summary>
        /// Gets or sets an object that will be passed to the <see cref="Command" /> attached to this trigger. This is a DependencyProperty.
        /// </summary>
        public object CommandParameter
        {
            get { return GetValue(CommandParameterProperty); }
            set { SetValue(CommandParameterProperty, value); }
        }

        /// <summary>
        /// Gets or sets an object that will be passed to the <see cref="Command" />
        /// attached to this trigger. This property is here for compatibility
        /// with the Silverlight version. This is NOT a DependencyProperty.
        /// For databinding, use the <see cref="CommandParameter" /> property.
        /// </summary>
        public object CommandParameterValue
        {
            get { return commandParameterValue ?? CommandParameter; }
            set
            {
                commandParameterValue = value;
                EnableDisableElement();
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the attached element must be
        /// disabled when the <see cref="Command" /> property's CanExecuteChanged
        /// event fires. If this property is true, and the command's CanExecute 
        /// method returns false, the element will be disabled. If this property
        /// is false, the element will not be disabled when the command's
        /// CanExecute method changes. This is a DependencyProperty.
        /// </summary>
        public bool MustToggleIsEnabled
        {
            get { return (bool) GetValue(MustToggleIsEnabledProperty); }
            set { SetValue(MustToggleIsEnabledProperty, value); }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the attached element must be disabled when the <see cref="Command" /> property's CanExecuteChanged
        /// event fires. If this property is true, and the command's CanExecute  method returns false, the element will be disabled. 
        /// This property is here for compatibility with the Silverlight version. This is NOT a DependencyProperty.
        /// For databinding, use the <see cref="MustToggleIsEnabled" /> property.
        /// </summary>
        public bool MustToggleIsEnabledValue
        {
            get { return mustToggleValue == null ? MustToggleIsEnabled : mustToggleValue.Value; }
            set
            {
                mustToggleValue = value;
                EnableDisableElement();
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the EventArgs passed to the event handler will be forwarded to the ICommand's Execute method
        /// when the event is fired (if the bound ICommand accepts an argument of type EventArgs).
        /// </summary>
        public bool PassEventArgsToCommand { get; set; }

        /// <summary>
        /// Invokes the action.
        /// </summary>
        /// <param name="parameter">The parameter to the action. If the action does not require a parameter, 
        /// the parameter may be set to a null reference.</param>
        protected override void Invoke(object parameter = null)
        {
            if (!AssociatedElementIsDisabled())
            {
                var command = GetCommand();
                var commandParameter = CommandParameterValue;
                if (commandParameter == null && PassEventArgsToCommand)
                {
                    commandParameter = parameter;
                }
                if (command != null && command.CanExecute(commandParameter))
                {
                    command.Execute(commandParameter);
                }
            }
        }

        /// <summary>
        /// Called after the action is attached to an AssociatedObject.
        /// </summary>
        protected override void OnAttached()
        {
            base.OnAttached();
            EnableDisableElement();
        }

        private static void OnCommandChanged(EventCommand element, DependencyPropertyChangedEventArgs e)
        {
            if (element != null)
            {
                if (e.OldValue != null)
                {
                    ((ICommand) e.OldValue).CanExecuteChanged -= element.OnCommandCanExecuteChanged;
                }
                var command = (ICommand) e.NewValue;
                if (command != null)
                {
                    command.CanExecuteChanged += element.OnCommandCanExecuteChanged;
                }
                element.EnableDisableElement();
            }
        }

        private FrameworkElement GetAssociatedObject()
        {
            return AssociatedObject as FrameworkElement;
        }

        private ICommand GetCommand()
        {
            return Command;
        }

        private bool AssociatedElementIsDisabled()
        {
            var element = GetAssociatedObject();
            return AssociatedObject == null || (element != null && !element.IsEnabled);
        }

        private void EnableDisableElement()
        {
            var element = GetAssociatedObject();
            if (element != null)
            {
                var command = GetCommand();
                if (MustToggleIsEnabledValue && command != null)
                {
                    element.IsEnabled = command.CanExecute(CommandParameterValue);
                }
            }
        }

        private void OnCommandCanExecuteChanged(object sender, EventArgs e)
        {
            EnableDisableElement();
        }
    }
}